package com.sarc.games.focus;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

import javax.microedition.khronos.opengles.GL10;

import org.opencv.core.CvType;
import org.opencv.core.Mat;
import android.media.MediaPlayer;
import android.opengl.GLSurfaceView;
import android.view.MotionEvent;
import android.widget.Toast;

import com.sarc.R;
import com.sarc.games.GameAction;
import com.sarc.games.GameSkeleton;
import com.sarc.games.ResourceTexturedQuad;
import com.sarc.games.SensorMathematics;
import com.sarc.games.Timeline;

import com.sarc.games.focus.ColorManager.ColorSet;
import com.sarc.games.focus.ColorManager.ColorType;
import com.sarc.games.focus.Square.SquareStatus;

public class FocusGame extends GameSkeleton {

	//TODO: implement states of the game
	//TODO: implement energy and area checking --> For altering square energy level
	//TODO: 
	
	protected static final String TAG = "Focus Game";
	final long ANIMATION_PERIOD = 200;
	
	static Mat trianglePointRv;
	static Mat trianglePoint1Tv;
	static Mat trianglePoint2Tv;
	static Mat trianglePoint3Tv;
	
	Grid grid;
	Energy energy;
	
	long frameTime;
	ReentrantLock lock;
	ColorManager colorManager;
	int currentLevel;
	FocusTimeline timeline;
	BlurAction blurAction;
	GameStatus gameStatus;
	SquareStatus gridStatus;
	MediaPlayer bgMediaPlayer;
	
	boolean guiPreloaded = false;
	HashMap<String, ResourceTexturedQuad> texturedQuads;
	
	enum GameStatus
	{
		SHOWING_TITLE_SCREEN, SHOWING_GUI, PREPARING_GAME, GAME_IN_PROGRESS, UPDATING_GAME, RENDERING_GAME, GAME_WON, GAME_LOST
	}
	
	public FocusGame(GLSurfaceView view, SensorMathematics sensor, float screenWidth, float screenHeight) {
		super(view, sensor, screenWidth, screenHeight);
		
		//Initializing virtual 3d objects
		trianglePoint1Tv = Mat.zeros(3, 1, CvType.CV_64FC1);
		trianglePoint2Tv = Mat.zeros(3, 1, CvType.CV_64FC1);
		trianglePoint2Tv.put(0, 0, new double[]{1});
		trianglePoint3Tv = Mat.zeros(3, 1, CvType.CV_64FC1);
		trianglePoint3Tv.put(1, 0, new double[]{1});
		trianglePointRv = Mat.eye(3, 3, CvType.CV_64FC1);
		
		//initializing game objects
		grid = null;
		energy = null;
		lock = new ReentrantLock();
		currentLevel = 0;
		timeline = null;
		blurAction = null;
		
		//oh color manager!
		
		//Set 1 for level 1
		colorManager = new ColorManager();
		ColorSet set1 = colorManager.new ColorSet();
		set1.setColorByType(ColorType.GIVE, new int[]{52,99,163,255});
		set1.setColorByType(ColorType.TAKE, new int[]{254,117,25,255});
		set1.setColorByType(ColorType.NORMAL, new int[]{107,190,217,255});
		set1.setColorByType(ColorType.BOUNDARY, new int[]{42,52,113,128});
		set1.setColorByType(ColorType.ENERGY, new int[]{255,255,255,255});
		colorManager.addNewColorTheme(colorManager.new ColorTheme(set1));
		
		
		//Set 2 for level 2
		ColorSet set2 = colorManager.new ColorSet();
		set2.setColorByType(ColorType.GIVE, new int[]{83,16,86,255});
		set2.setColorByType(ColorType.TAKE, new int[]{48,105,44,255});
		set2.setColorByType(ColorType.NORMAL, new int[]{196,38,19,255});
		set2.setColorByType(ColorType.BOUNDARY, new int[]{42,52,113,128});
		set2.setColorByType(ColorType.ENERGY, new int[]{236,173,82,255});
		colorManager.addNewColorTheme(colorManager.new ColorTheme(set2));
		
		colorManager.setColorTheme(0, 0);
		
		//Loading all the GUI components from Android resource.
		float widthHeightRatio = 1;
		texturedQuads = new HashMap<String, ResourceTexturedQuad>();
		
		//Testing Texture
		texturedQuads.put("star", new ResourceTexturedQuad(getView().getContext(), R.drawable.star, widthHeightRatio));
		
		//Intro screen, 1024x512
		texturedQuads.put("introScreen", new ResourceTexturedQuad(getView().getContext(), R.drawable.intro_screen, widthHeightRatio));
		
		//Game won, 
		texturedQuads.put("gameWon", new ResourceTexturedQuad(getView().getContext(), R.drawable.game_won, widthHeightRatio));
		
		//Description of game won
		texturedQuads.put("gameWonDesc", new ResourceTexturedQuad(getView().getContext(), R.drawable.game_won_desc, widthHeightRatio));
		texturedQuads.get("gameWonDesc").setDimensions(0, 1, 1, 1);
		
		//Game Lost, 
		texturedQuads.put("gameLost", new ResourceTexturedQuad(getView().getContext(), R.drawable.game_lost, widthHeightRatio));
		
		//Description of Game Lost, overpowered/overgiven, 
		texturedQuads.put("overPoweredDesc", new ResourceTexturedQuad(getView().getContext(), R.drawable.overpowered_desc, widthHeightRatio));
		texturedQuads.get("overPoweredDesc").setDimensions(0, 1, 1, 1);
		
		//Description of Game Lost, underpowered/overtaken, 
		texturedQuads.put("underPoweredDesc", new ResourceTexturedQuad(getView().getContext(), R.drawable.underpowered_desc, widthHeightRatio));
		texturedQuads.get("underPoweredDesc").setDimensions(0, 1, 1, 1);
		
		//Tap for retry...
		texturedQuads.put("tapForRetry",  new ResourceTexturedQuad(getView().getContext(), R.drawable.tap_for_retry, widthHeightRatio));
		
		//Tap for a new level
		texturedQuads.put("tapForLevel",  new ResourceTexturedQuad(getView().getContext(), R.drawable.tap_for_another_level, widthHeightRatio));
				
		gameStatus = GameStatus.SHOWING_TITLE_SCREEN;
		gridStatus = SquareStatus.NORMAL;
	}
	
	private void updateGame() {
		
		//To get everything synchronized, this is important
		//Everything updated in this will base on this frame time.
		frameTime = Calendar.getInstance().getTimeInMillis();
		
		colorManager.update(frameTime);
		
		if (blurAction != null)
		{
			blurAction.update(frameTime);
			if (blurAction.shouldRemove()) 
			{
				blurAction = null;
			}
		}
		
		switch (gameStatus)
		{
			case GAME_IN_PROGRESS:
				
				//Give an update only if game is progressing
				gameStatus = GameStatus.UPDATING_GAME;
				if (grid != null)
				{
					//update the timeline!
					timeline.update(frameTime);
										
					//game is finished
					if (timeline.shouldRemove())
					{
						//game is over since timeline finished, so i should tidy up...
						grid = null;
						timeline = null;
						float[] endColor = colorManager.getCurrentColorTheme().getBasicColorSet().getColorByType(ColorType.ENERGY);
						blurAction = new BlurAction(300, 15, endColor);
						gameStatus = GameStatus.GAME_WON;
						makeToastMessage("Touch again for a new game...", Toast.LENGTH_SHORT);
					}
					else
					{
						gridStatus = grid.update(energy, frameTime, colorManager);
						
						switch (gridStatus)
						{
							case OVERGIVEN:
								//Too much energy and I am full. End the Game!
								grid = null;
								timeline = null;
								float[] giveColor = colorManager.getCurrentColorTheme().getBasicColorSet().getColorByType(ColorType.GIVE);
								blurAction = new BlurAction(300, 15, giveColor);
								gameStatus = GameStatus.GAME_LOST;
								makeToastMessage("You lost since you have too much energy...\nTouch to continue...", Toast.LENGTH_SHORT);
								break;
							case OVERTAKEN:
								//Not enough energy and I am hungry. End the Game!
								grid = null;
								timeline = null;
								float[] takeColor = colorManager.getCurrentColorTheme().getBasicColorSet().getColorByType(ColorType.TAKE);
								blurAction = new BlurAction(300, 15, takeColor);
								gameStatus = GameStatus.GAME_LOST;
								makeToastMessage("You lost since you have too few energy...\nTouch to continue...", Toast.LENGTH_SHORT);
								break;
							default:
								//Game should process...
								gameStatus = GameStatus.GAME_IN_PROGRESS;
								break;
						}
					}
				}	
				break;
				
			case SHOWING_TITLE_SCREEN:
			case SHOWING_GUI:
				//Give random blurring effects of the background, if there's no current blurring effect
				if (blurAction == null)
				{
					ArrayList<float[]> colors = new ArrayList<float[]>();
					for (ColorType colorType : ColorType.values())
						colors.add(colorManager.getCurrentColorTheme().getBasicColorSet().getColorByType(colorType));
					Random rand = new Random();
					blurAction = new BlurAction(2000, rand.nextInt(15), colors.get(rand.nextInt(colors.size())));
				}
				break;
			default: 
				break;
		}
	}
	
	protected void renderGame(GL10 gl, float scaleX, float scaleY, float distZ)
	{
		lock.lock();			
		
		try
		{
			gameStatus = GameStatus.RENDERING_GAME;
			gl.glPushMatrix();
			gl.glScalef(scaleX, scaleY, 1f);
			gl.glTranslatef(0, 0, distZ);	
			grid.render(gl);
			
		}
		finally
		{
			gl.glPopMatrix();
			gameStatus = GameStatus.GAME_IN_PROGRESS;
			lock.unlock();
		}
	}

	//used to render GUI components -- Currently used as debugging. 
	@Override
	protected void drawGamePreScene(GL10 gl) 
	{
		// protecting it from render thread & game logic thread & interaction thread
		lock.lock();
		
		//this part is the game logic and rendering combined -- game logic should be separated from rendering thread later for performance.

		try
		{
			//TODO: separate the update part to another thread
			updateGame();
			
			//Preload all GUI Components into memory so that it will be rendered faster later.
			if (!guiPreloaded)
			{
				for (ResourceTexturedQuad quad: texturedQuads.values())
				{
					quad.preload(gl);
				}
				guiPreloaded = true;
			}
			
			switch (gameStatus)
			{
				case SHOWING_TITLE_SCREEN:
					texturedQuads.get("introScreen").render(gl);
					break;
				case SHOWING_GUI:
					texturedQuads.get("star").render(gl);
					break;
				case GAME_WON:
					//TODO: Game won texture
					texturedQuads.get("gameWon").render(gl);
					texturedQuads.get("gameWonDesc").render(gl);
					break;
				case GAME_LOST:
					//TODO: Game lost texture
					texturedQuads.get("gameLost").render(gl);
					if (gridStatus == SquareStatus.OVERGIVEN)
					{
						texturedQuads.get("overPoweredDesc").render(gl);
					}
					else if (gridStatus == SquareStatus.OVERTAKEN)
					{
						texturedQuads.get("underPoweredDesc").render(gl);
					}
					break;
				default:
					break;
			}
		}
		finally
		{
			lock.unlock();
		}
	}

	//used to render game components -- right now is just triangles.
	@Override
	protected void drawGameScene(GL10 gl) 
	{		
		lock.lock();
		try
		{
			//game objects for testing
			if (gameStatus == GameStatus.GAME_IN_PROGRESS)
			{
				renderGame(gl, 0.1f, 0.1f, 0f); 
				NativeRenderTriangle(trianglePoint1Tv.nativeObj, trianglePointRv.nativeObj);
				NativeRenderTriangle(trianglePoint2Tv.nativeObj, trianglePointRv.nativeObj);
				NativeRenderTriangle(trianglePoint3Tv.nativeObj, trianglePointRv.nativeObj);
			}
		}
		finally
		{
			lock.unlock();
		}
	}

	//notification that the game scene shouldn't be drawn.
	@Override
	protected void onShouldDrawSceneChanged(boolean shouldDrawScene) 
	{
		if (shouldDrawScene)
		{
			//Just started the game, make a warm welcoming message
			makeToastMessage("Welcome to FOCUS. Tap Anywhere to start...", Toast.LENGTH_LONG);
		}
		else
		{
			//TODO: do something to stop the game components from running so that it won't eat up performance
		}
	}
	
	public boolean onUserTouch(MotionEvent event)
	{
		boolean consumed = false;
		Random rand = new Random();
		if (event.getAction() == MotionEvent.ACTION_DOWN)
		{
			//called from another thread
			//protect me!	
			lock.lock();
			try
			{
				
				switch (gameStatus)
				{
					case SHOWING_GUI:
					case SHOWING_TITLE_SCREEN:
						makeToastMessage("New game started...",Toast.LENGTH_SHORT);
						createNewGame();
						break;
						
					case GAME_LOST:
					case GAME_WON:
						
						colorManager.setColorTheme(300, 0);
						gameStatus = GameStatus.SHOWING_GUI;
						break;
					case PREPARING_GAME:
						break;
						
					//Game in progress
					default:
						if (event.getRawY() > screenHeight / 2)
						{
							//right hand side
							if (event.getRawX() > screenWidth / 2)
							{
								//top right
								if (rand.nextBoolean())
								{
									grid.randomGive(0.5, 200);
								}
								else if (rand.nextBoolean())
								{
									grid.randomTake(0.3, 400);
								}
								else
								{
									grid.refreshGrid(rand.nextFloat());
								}
								
							}		
							else 
							{
								//bottom right
								grid.rotateAllSquare(rand.nextBoolean(), 300);
							}
						}
						else
						{
							//left hand side
							if (event.getRawX() > screenWidth / 2)	
							{
								//top left
								grid.populate(200);
							}
							else 
							{
								//top right
								grid.shuffleGrid(200);
							}
							
						}
						break;
				}
				consumed = true;
			}
		
			finally
			{
				lock.unlock();
			}
		}
		return consumed;
	}
	
	private void createNewGame() {
		
		gameStatus = GameStatus.PREPARING_GAME;
		
		Random rand = new Random();
		grid = new Grid(rand.nextInt(4) + 2);
		colorManager.setColorTheme(0, 0);
		blurAction = new BlurAction(300, 1, new float[]{1f, 1f, 1f, 1f});
		
		//create testing timeline
		timeline = new FocusTimeline(10000);
		timeline.addGiveAction(1000, 0, 0, 0.3, ANIMATION_PERIOD);
		timeline.addTakeAction(1300, 0, 0, 0.3, ANIMATION_PERIOD);
		timeline.addPopulateAction(1500, ANIMATION_PERIOD);
		timeline.addRefreshAction(2500, 0);
		timeline.addRandomGiveAction(3000, 0.3, ANIMATION_PERIOD);
		timeline.addRandomTakeAction(3100, 0.3, ANIMATION_PERIOD);
		timeline.addRotateSquareAction(3300, rand.nextBoolean() , ANIMATION_PERIOD);
		timeline.addShuffleAction(4000, ANIMATION_PERIOD);
		timeline.changeLevel(5000, 1, ANIMATION_PERIOD);
		timeline.addTakeAction(6000, 0, 0, 0.9, ANIMATION_PERIOD);
		timeline.addRandomGiveAction(7000, 0.5, ANIMATION_PERIOD);
				
		//TODO: LOAD BACKGROUND MUSIC
		bgMediaPlayer = new MediaPlayer();

		//TODO: LOAD TIME LINE FOR GAME <Automating creation of timeline from file>
		
		//ok the game is ready to be started.
		gameStatus = GameStatus.GAME_IN_PROGRESS;
	}
		
	private void makeToastMessage(final String message, final int length)
	{
		getView().post(new Runnable()
		{
			@Override
			public void run() {
				Toast.makeText(getView().getContext(), message, length).show();
			}
		});
		
	}
	
	private class BlurAction extends GameAction
	{
		
		int fromLevel, toLevel;
		float[] color1, color2;
		
		public BlurAction(long timeAllowed, int targetBlurLevel, float[] targetColor) 
		{
			super(timeAllowed);
			this.fromLevel = getBackgroundBlurLevel();
			this.toLevel = targetBlurLevel;
			this.color1 = getBackgroundColor();
			this.color2 = targetColor;
			
			// TODO Auto-generated constructor stub
		}
		
		public boolean update(long frameTime)
		{
			if (super.resolve(frameTime))
			{
				float[] newColor = ColorManager.mixColor(color1, color2, this.currentProgress());
				int newSize;
				if (toLevel > fromLevel)
				{
					newSize = (int)((float)(toLevel - fromLevel) * (this.currentProgress())) + fromLevel;
				}
				else
				{
					newSize = fromLevel - (int)((float)(fromLevel - toLevel) * this.currentProgress());
				}
				setBackgroundEffects(newSize, newColor);
				
				return true;
			}
			else
			{
				return false;
			}
		}
	}

	private class FocusTimeline extends Timeline 
	{
		
		public FocusTimeline(long timeAllowed) {
			super(timeAllowed);
			// TODO Auto-generated constructor stub
		}
		
		public void addGiveAction(long eventTime, final int x, final int y, final double energy, final long eventDuration)
		{
			super.addAction(eventTime, new TimelineAction()
			{
				public void doAction(){grid.giveAt(x, y, energy, eventDuration);}
			});
		}
		
		public void addTakeAction(long eventTime, final int x, final int y, final double energy, final long eventDuration)
		{
			super.addAction(eventTime, new TimelineAction()
			{
				public void doAction(){grid.takeAt(x, y, energy, eventDuration);}
			});
		}
		
		public void addRandomGiveAction(long eventTime, final double energy, final long eventDuration)
		{
			super.addAction(eventTime, new TimelineAction()
			{
				public void doAction(){grid.randomGive(energy, eventDuration);}
			});
		}
		
		public void addRandomTakeAction(long eventTime, final double energy, final long eventDuration)
		{
			super.addAction(eventTime, new TimelineAction()
			{
				public void doAction(){grid.randomTake(energy, eventDuration);}
			});
		}
		
		public void addPopulateAction(long eventTime, final long eventDuration)
		{
			super.addAction(eventTime, new TimelineAction()
			{
				public void doAction(){grid.populate(eventDuration);}
			});
		}
		
		public void addShrinkAction(long eventTime, final double energy, final long eventDuration)
		{
			super.addAction(eventTime, new TimelineAction()
			{
				//TODO: implementation required
				public void doAction(){ /* unimplemented */}
			});
		}
		
		public void addRefreshAction(long eventTime, final double newEnergyLevel)
		{
			super.addAction(eventTime, new TimelineAction(){
				public void doAction(){grid.refreshGrid(newEnergyLevel);}
			});
		}
		
		public void addRadiateAction(long eventTime, final long eventDuration)
		{
			super.addAction(eventTime, new TimelineAction()
			{
				//TODO: implementation required
				public void doAction(){ /* unimplemented */}
			});
		}
		
		public void addSwirlAction(long eventTime, final long eventDuration)
		{
			super.addAction(eventTime, new TimelineAction()
			{
				//TODO: implementation required
				public void doAction(){ /* unimplemented */}
			});
		}
		
		public void addShuffleAction(long eventTime, final long eventDuration)
		{
			super.addAction(eventTime, new TimelineAction()
			{
				public void doAction(){grid.shuffleGrid(eventDuration);}
			});
		}
		
		public void addRotateSquareAction(long eventTime, final boolean isClockwise, final long eventDuration)
		{
			super.addAction(eventTime,  new TimelineAction()
			{
				public void doAction(){grid.rotateAllSquare(isClockwise, eventDuration);}
			});
		}
		
		public void addRotateWholeAction(long eventTime, final boolean isClockwise, final long eventDuration)
		{
			super.addAction(eventTime, new TimelineAction()
			{
				public void doAction(){grid.rotateWhole(isClockwise, eventDuration);}
			});
		}
		
		public void changeLevel(long eventTime, final int newLevel, final long eventDuration)
		{
			super.addAction(eventTime, new TimelineAction()
			{

				@Override
				public void doAction() {
					// TODO Auto-generated method stub
					colorManager.setColorTheme(eventDuration, newLevel);
				}
				
			});
		}
	}
}